package middleware

import (
	"engine/cache"
	"engine/consts"
	"engine/dto"
	"errors"
	"lib"
	"lib/db"
	"lib/log"
	"lib/response"
	sf_ctx "lib/sf-ctx"
	sferrors "lib/sf-errors"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/redis/go-redis/v9"
)

func processToken(c *gin.Context, key string) (userId uint64, token string, err error) {
	authorization := c.GetHeader("Authorization")
	if authorization == "" {
		err = errors.New("authorization header is empty")
		return
	}

	tokenParts := strings.Split(authorization, " ")

	if len(tokenParts) != 2 || strings.ToLower(tokenParts[0]) != "bearer" {
		err = errors.New("invalid authorization header")
		return
	}

	token = tokenParts[1]

	claims := jwt.MapClaims{}
	parsedToken, parseErr := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
		return []byte(key), nil
	})

	if parseErr != nil {
		err = parseErr
		return
	}

	if !parsedToken.Valid {
		err = errors.New("invalid token")
		return
	}

	userId = uint64(claims["id"].(float64))

	if cachedID, redisErr := db.GetRedisClient().Get(c, token).Uint64(); redisErr != nil {
		if errors.Is(redisErr, redis.Nil) {
			err = errors.New("invalid token")
		} else {
			err = redisErr
		}
	} else if cachedID != userId {
		err = errors.New("invalid token")
	}
	return
}

func ProcessToken(c *gin.Context, key string) (userId uint64, token string, err error) {
	authorization := c.GetHeader("Authorization")
	if authorization == "" {
		err = errors.New("authorization header is empty")
		return
	}

	tokenParts := strings.Split(authorization, " ")

	if len(tokenParts) != 2 || strings.ToLower(tokenParts[0]) != "bearer" {
		err = errors.New("invalid authorization header")
		return
	}

	token = tokenParts[1]

	claims := jwt.MapClaims{}
	parsedToken, parseErr := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
		return []byte(key), nil
	})

	if parseErr != nil {
		err = parseErr
		return
	}

	if !parsedToken.Valid {
		err = errors.New("invalid token")
		return
	}

	userId = uint64(claims["id"].(float64))

	if cachedID, redisErr := db.GetRedisClient().Get(c, token).Uint64(); redisErr != nil {
		if errors.Is(redisErr, redis.Nil) {
			err = errors.New("invalid token")
		} else {
			err = redisErr
		}
	} else if cachedID != userId {
		err = errors.New("invalid token")
	}
	return
}

func SysAuthorizationMiddleware() gin.HandlerFunc {
	log.Info("使用管理后台认证中间件")
	return func(ctx *gin.Context) {
		userID, token, err := processToken(ctx, consts.AdminAuthKey)
		if err != nil {
			//response.Error(ctx, sferrors.New(err).SetCode(sferrors.InvalidTokenError).SetHttpStatus(http.StatusUnauthorized))
			//ctx.Abort()
			//return
			//response.Error(ctx, sferrors.NewStringf("请重新登录"))
			ctx.JSON(401, map[string]any{
				"code": 401,
				"msg":  "请重新登录",
			})
			ctx.Abort()
			return
		}
		if strings.Contains(ctx.FullPath(), "/me") == true {
			cacheKey := cache.GetUserCacheName(userID, consts.ModuleAdmin)
			if err = cache.RedisContextDelete(ctx, cacheKey); err != nil {
				log.Errorf("删除Redis失败，错误: %v", err)
			}
		}
		sysUser, err := dto.GetCachedActiveSysUserById(ctx, userID)
		if err != nil {
			ctx.JSON(401, map[string]any{
				"code": 401,
				"msg":  "请重新登录",
			})
			ctx.Abort()
			return
			//log.Errorf("获取CMS用户失败：%v", err)
			//response.Error(ctx, sferrors.New(err).SetCode(sferrors.SystemError).SetHttpStatus(http.StatusInternalServerError))
			//ctx.Abort()
			//return
		}
		// 用户可能被删除或者禁用了，这时候需要清除token
		if sysUser == nil || sysUser.Id == 0 {
			ctx.JSON(401, map[string]any{
				"code": 401,
				"msg":  "请重新登录",
			})
			ctx.Abort()
			return

			//response.Error(ctx, sferrors.Code(sferrors.InvalidUserError).SetHttpStatus(http.StatusUnauthorized))
			//ctx.Abort()
			//return
		}

		cache.RedisContextExpire(ctx, token, consts.TokenExpireTime)
		ctx.Set("value", &sf_ctx.CmsCtxValue{
			User: sysUser,
		})
		ctx.Set("module", consts.ModuleAdmin)

		locks := lib.RedisLock(strconv.Itoa(int(sysUser.Id)))
		if !locks {
			response.Errors(ctx, sferrors.NewString("操作频繁"))
			ctx.Abort()
			return
		}

		ctx.Next()
	}
}
